type Server {
    users:   Reactive[Map[String,User]],
    msg-bus: Bus[chat::#Message]
};

type User {
    conn: rpc::Connection
};

type Session impl(chat::#Service) {
    id:  String,
    ctx: Server
};

method Session.say: rpc::Method[chat::#MessageContent,()]
    &(session) => &(content) =>
        let msg := { |chat::#Message| {
            from: session.id,
            content
        } },
        (session.ctx.msg-bus.in <- msg);

method Session.receive-messages: rpc::Method*[(),chat::#Message]
    &(session) => &(_) =>
        session.ctx.msg-bus.out;

function new-session:
    &(Server,chat::#Login,rpc::Connection) => Async[Session,Error]
    &(ctx,login,conn) =>
        let { nickname } := login,
        let id := nickname,
        let user := { |User| { conn } },
        let try-login :=
            let { users } := ctx,
            \ do let old-users := users.out.sample,
            \ debug-log old-users,
            if (id not-in old-users):
                let new-users := (old-users insert (id, user)),
                \ do (users.in <- new-users),
                { return Yes },
            else:
                { return No },
        \ await let ok := try-login,
        if ok:
            { return { |Session| { ctx, id } } },
        else:
            { throw { Error {
                message: { "nickname '?' is in use" id },
                data: [(chat::ERR_KIND, chat::ERR_NAME_IN_USE)]
            } } };

function delete:
    &(Session) => Sync
    &{id,ctx} =>
        (ctx.users update &(old-users) => (old-users delete id));

entry { crash-on-error
    \ do let users := { Reactive { initial: { Map::[String,User] [] } } },
    \ do let msg-bus := { Bus::[chat::#Message] () },
    let ctx := { |Server| { users, msg-bus } },
    { rpc::serve {
        service: chat::#Service,
        backend: chat::DefaultServerBackend,
        options: {},
        constructor: &(arg,conn) =>
            \ await let session := { new-session (ctx,arg,conn) },
            { return { |chat::#Service| session } }
                . { with conn
                    .{ on-close }
                    .{ catch &(_) => { return () } }
                    .{ await &() => { delete session } } }
    } }
};
